#include "stdlib.hpp"
#include "libfile.hpp"
#include <purewell/spinlock.hpp>

namespace purewell
{

#ifdef __PW_HAVE_POSIX_SPIN
typedef struct __pw_spinlock_t
{
	pthread_spinlock_t		lock;
	bool					pshared;
	int						fd;

	inline __pw_spinlock_t() : pshared(false), fd(-1) {}
} __pw_spinlock_t;

#	define _xchg() &(((__pw_spinlock_t*)(m_pData))->lock)
#endif//!__PW_HAVE_POSIX_SPIN

#ifdef __PW_HAVE_POSIX_SPIN

inline
static
void
__PWDestroySpinLock(__pw_spinlock_t* dat)
{
	pthread_spin_destroy(&(dat->lock));
	if ( dat->pshared )
	{
		__pw_shinst_t si;
		si.p = dat;
		si.fd = dat->fd;
		si.size = sizeof(__pw_spinlock_t);
		__PWDestroySharedInstance(si);
	}
	else
	{
		delete dat;
	}
}

inline
static
__pw_spinlock_t*
__PWCreateSpinLockShared(void)
{
	__pw_spinlock_t* dat(NULL);
	__pw_shinst_t si;

	do
	{
		if ( !__PWCreateSharedInstance(si, sizeof(__pw_spinlock_t)) )
		{
			break;
		}

		dat = (__pw_spinlock_t*)si.p;

		if ( 0 != pthread_spin_init(&(dat->lock),
			PTHREAD_PROCESS_SHARED) )
		{
			dat = NULL;
			break;
		}

		dat->pshared = true;
		dat->fd = si.fd;

		return dat;
	} while (false);

	if ( si.p )
	{
		__PWDestroySharedInstance(si);
	}

	return NULL;
}

inline
static
__pw_spinlock_t*
__PWCreateSpinLockPrivate(void)
{
	__pw_spinlock_t* dat(NULL);

	do
	{
		dat = new __pw_spinlock_t;
		if ( NULL == dat ) break;

		if ( 0 != pthread_spin_init(&(dat->lock),
			PTHREAD_PROCESS_PRIVATE) )
		{
			break;
		}

		return dat;
	} while (false);

	if ( dat )
	{
		delete dat;
		dat = NULL;
	}

	return NULL;
}

#endif//!__PW_HAVE_POSIX_SPIN


#if !defined(__PW_HAVE_POSIX_SPIN_TIMEDLOCK) && defined(__PW_HAVE_POSIX_SPIN)
int
pthread_spin_timedlock(pthread_spinlock_t* lock,
	const struct timespec* ts)
{
	uint64_t now;
	struct timeval tv;
	int res;

	gettimeofday(&tv, NULL);
	uint64_t lim((ts->tv_sec * 1000000ULL + (ts->tv_nsec / 1000ULL))
		+ (tv.tv_sec * 1000ULL + tv.tv_usec));

	do
	{
		res = pthread_spin_trylock(lock);
		if ( res == 0 ) break;
		else if ( res == EDEADLK )
		{
			gettimeofday(&tv, NULL);
			now = tv.tv_sec * 1000ULL + tv.tv_usec;
			if ( now > lim )
			{
				return ETIMEDOUT;
			}

			continue;
		}
		else
		{
			return res;
		}
	} while (true);

	return 0;
}
#endif//!__PW_HAVE_POSIX_SPIN_TIMEDLOCK && __PW_HAVE_POSIX_SPIN

void
PWSpinLock::lock(void)
{
#ifdef __PW_HAVE_POSIX_SPIN
	pthread_spin_lock(_xchg());
#else
#	ifdef WIN32
	EnterCriticalSection((LPCRITICAL_SECTION)m_pData);
#	else
	__PWERRNO_NOIMPL();
#	endif
#endif
}

void
PWSpinLock::unlock(void)
{
#ifdef __PW_HAVE_POSIX_SPIN
	pthread_spin_unlock(_xchg());
#else
#	ifdef WIN32
	LeaveCriticalSection((LPCRITICAL_SECTION)m_pData);
#	else
	__PWERRNO_NOIMPL();
#	endif
#endif
}

bool
PWSpinLock::lockTest(void)
{
#ifdef __PW_HAVE_POSIX_SPIN
	return 0 == pthread_spin_trylock(_xchg());
#else
#	ifdef WIN32
	return 0 != TryEnterCriticalSection((LPCRITICAL_SECTION)m_pData);
#	else
	__PWERRNO_NOIMPL();
	return false;
#	endif
#endif
}

bool
PWSpinLock::lockTimeLimit(size_t msec)
{
#ifdef __PW_HAVE_POSIX_SPIN_TIMEDLOCK
	struct timespec ts;
	ts.tv_sec = msec / 1000;
	ts.tv_nsec = (msec % 1000) * 1000000LL;

	return 0 == pthread_spin_timedlock(_xchg(), &ts);
#else
#	ifdef WIN32
	__PWERRNO_NOIMPL();
	return false;
#	else
	const uint64_t begin(__PWGetNow());
	uint64_t now;

	do
	{
		if ( lockTest() ) return true;
		now = __PWGetNow();
	} while ( begin - now < (uint64_t)msec );

	errno = ETIMEDOUT;
	return false;
#	endif// WIN32
#endif
}

PWSpinLock*
PWSpinLock::create(bool pshared)
{
#ifdef __PW_HAVE_POSIX_SPIN
	__pw_spinlock_t* dat(NULL);
	PWSpinLock* cls(NULL);

	do
	{
		if ( NULL == (cls = new PWSpinLock()) )
		{
			break;
		}

		dat = pshared ? __PWCreateSpinLockShared()
			: __PWCreateSpinLockPrivate();
		if ( NULL == dat ) break;

		cls->m_pData = dat;
		return cls;

	} while (false);

	if ( cls )
	{
		delete cls;
	}

	return NULL;
#else
#	ifdef WIN32
	if ( pshared ) return NULL; // not support.

	CRITICAL_SECTION* dat(NULL);
	PWSpinLock* cls(NULL);

	do
	{
		if ( NULL == (cls = new PWSpinLock) ) break;
		if ( NULL == (dat = new CRITICAL_SECTION) ) break;
		if ( FALSE == InitializeCriticalSectionAndSpinCount(dat,
			2000) ) break;

		cls->m_pData = dat;
		return cls;

	} while (false);

	if ( cls ) delete cls;
	if ( dat ) delete dat;

	return NULL;

#	else
	__PWERRNO_NOIMPL();
	return NULL;
#	endif//WIN32
#endif//__PW_HAVE_POSIX_SPIN
}

void
PWSpinLock::release(PWSpinLock* lock)
{
	delete lock;
}

PWSpinLock::PWSpinLock()
{
}

PWSpinLock::~PWSpinLock()
{
#ifdef __PW_HAVE_POSIX_SPIN
	if ( NULL == m_pData ) return;
	__PWDestroySpinLock((__pw_spinlock_t*)m_pData);
	m_pData = NULL;
#else
#	ifdef WIN32
	if ( NULL == m_pData ) return;
	DeleteCriticalSection((LPCRITICAL_SECTION)m_pData);
	m_pData = NULL;
#	else
	// do nothing...
#	endif//WIN32
#endif
}

};//namespace purewell

